package com.easylinkin.sm.service.impl;

import com.easylinkin.sm.constant.LogConstant.Module;
import com.easylinkin.sm.constant.MenuConstant.Error;
import com.easylinkin.sm.entity.Menu;
import com.easylinkin.sm.entity.Menu.Fields;
import com.easylinkin.sm.mapper.MenuMapper;
import com.easylinkin.sm.repository.MenuRepository;
import com.easylinkin.sm.service.MenuService;

import java.util.List;
import java.util.Locale;
import java.util.Optional;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import site.morn.boot.data.CrudServiceSupport;
import site.morn.boot.jpa.SpecificationFunction;
import site.morn.boot.rest.RestPage;
import site.morn.core.CriteriaMap;
import site.morn.exception.ApplicationMessages;
import site.morn.log.OperateArguments;
import site.morn.rest.RestModel;
import site.morn.rest.RestPageable;

/**
 * 菜单服务
 *
 * @author wujie
 * @since 0.1.1,  2019/08/19
 */
@Slf4j
@Service
@Transactional
public class MenuServiceImpl extends CrudServiceSupport<Menu, Long, MenuRepository> implements
    MenuService {

  @Autowired
  private MenuMapper menuMapper;

  @Override
  public void patchHide(List<String> searchCodes) {
    if (null == searchCodes) {
      throw ApplicationMessages.translateException(Error.SEARCH_CODE_NOT_NULL).exception();
    }
    searchCodes.forEach(x -> this.repository().patchHide(x + "%"));
  }

  @Override
  public <S extends Menu> S add(RestModel<S> restModel) {
    S model = restModel.getModel();
    model.setDisplay(true);
    // 自动生成id、searchCode
    Long parentId = model.getParentId();
    if (null == parentId) {
      throw ApplicationMessages.translateException(Error.PARENT_ID_NOT_NULL).exception();
    }
    Optional<Menu> optional = this.repository().findById(parentId);
    if (optional.isPresent()) {
      Menu parentMenu = optional.get();
      S add = super.add(restModel);
      OperateArguments.add(model.getName());
      String searchCode = parentMenu.getSearchCode() + add.getId() + "!";
      add.setSearchCode(searchCode);
      return add;
    } else {
      throw ApplicationMessages.translateException(Error.PARENT_MENU_NOT_NULL).exception();
    }
  }

  @Override
  public <S extends Menu> S update(RestModel<S> restModel) {
    S model = restModel.getModel();
    Long parentId = model.getParentId();
    if (null == parentId) {
      throw ApplicationMessages.translateException(Error.PARENT_ID_NOT_NULL).exception();
    }

    Optional<Menu> menuOptional = this.repository().findById(model.getId());
    if (!menuOptional.isPresent()) {
      throw ApplicationMessages.translateException(Error.MENU_NOT_EXIST).exception();
    }
    Menu menu = menuOptional.get();
    // 如果修改了父类菜单，需要修改下面所有菜单的searchCode
    if (menu.getParentId().longValue() != parentId.longValue()) {
      Optional<Menu> optional = this.repository().findById(parentId);
      if (!optional.isPresent()) {
        throw ApplicationMessages.translateException(Error.PARENT_MENU_NOT_NULL).exception();
      }
      Menu parentMenu = optional.get();
      String parentSearchCode = parentMenu.getSearchCode();
      String modelSearchCode = menu.getSearchCode();
      // 不允许将上级菜单设置为当前菜单或当前菜单的子集
      if (parentSearchCode.startsWith(modelSearchCode)) {
        throw ApplicationMessages.translateException(Error.NOT_ALLOW_SET_PARENT_NODE).exception();
      }

      String newSearchCode = parentSearchCode + model.getId() + "!";
      // 更新所有子节点的searchCode
      this.updateSearchCode(newSearchCode, modelSearchCode);
      model.setSearchCode(newSearchCode);
    }
    S update = super.update(restModel);
    OperateArguments.add(model.getName());
    return update;
  }

  @Override
  public List<Menu> findByPrivilegeIds(List<Long> privilegeIds) {
    Locale locale = LocaleContextHolder.getLocale();
    Menu menu = new Menu();
    menu.setPrivilegeCodes(privilegeIds);
    menu.setDisplay(true);
    return menuMapper.getMenuAll(menu, Module.MENU, locale.toString().toUpperCase());
  }

  @Override
  public List<Menu> searchAll() {
    Locale locale = LocaleContextHolder.getLocale();
    Menu menu = new Menu();
    menu.setDisplay(true);
    return menuMapper.getMenuAll(menu, Module.MENU, locale.toString().toUpperCase());
  }

  @Override
  public List<Menu> searchAll(RestModel<Menu> restModel) {
    restModel.getModel().setDisplay(true);
    return super.searchAll(restModel);
  }

  @Override
  public Page<Menu> search(RestPage<Menu> restPage) {
    Menu model = restPage.getModel();
    RestPageable pageable = restPage.getPageable();

    com.baomidou.mybatisplus.extension.plugins.pagination.Page page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page(
        pageable.getPage() + 1L, pageable.getSize());
    model.setDisplay(true);
    Locale locale = LocaleContextHolder.getLocale();
    List<Menu> menuPage = menuMapper
        .getMenuPage(model, Module.MENU, locale.toString().toUpperCase(), page);
    page.setRecords(menuPage);

    PageRequest pageRequest = restPage.generatePageRequest();
    return new PageImpl(menuPage, pageRequest, page.getTotal());
  }

  @Override
  protected SpecificationFunction searchSpecificationFunction(Menu model, CriteriaMap attach) {
    return (reference, restrain, predicate) -> {
      Root root = reference.root();
      CriteriaBuilder builder = reference.builder();
      reference.query().orderBy(builder.asc(root.get(Fields.orderNo)));

      List<Long> privilegeCodes = model.getPrivilegeCodes();
      if (!CollectionUtils.isEmpty(privilegeCodes)) {
        restrain.appendAnd(predicate.in(Fields.privilegeCode, Fields.privilegeCodes));
      }
      Predicate[] equalAll = predicate.equalAll();
      restrain.appendAnd(equalAll);
    };
  }

  /**
   * 更新所有子节点的searchCode
   *
   * @param newSearchCode 新值
   * @param oldSearchCode 旧值
   */
  private void updateSearchCode(String newSearchCode, String oldSearchCode) {
    this.repository().updateSearchCode(newSearchCode, oldSearchCode);
  }

}
